﻿using System;
using System.Reflection;
using System.Collections;
using System.Collections.Generic;

namespace Narwhunderful.Aphelia.Framework.IO.Serialization
{
 
    public sealed class PropertyFactory
    {

        private readonly object[] _emptyObjectArray = new object[0];
        private readonly PropertyProvider _propertyProvider;

        private IDictionary<object, ComplexProperty> nonDuplicateValues = new Dictionary<object, ComplexProperty>();

        private int nextReferenceId = 1;

        public PropertyFactory(PropertyProvider propertyProvider)
        {
            _propertyProvider = propertyProvider;
        }

        public Property CreateProperty(string name, object value)
        {
            if (value == null) return new NullProperty(name);

            TypeInfo typeInfo = TypeInfo.GetTypeInfo(value);
            Property property = CreateSimpleProperty(name, typeInfo, value);

            if (property != null)
            {
                // It is simple type
                return property;
            }

            // Is it array?
            if (typeInfo.IsArray)
            {
                if (typeInfo.ArrayDimensionCount < 2)
                {
                    // 1D-Array
                    property = CreateSingleDimensionalArrayProperty(name, typeInfo, value);
                }
                else
                {
                    // MultiD-Array
                    property = CreateMultiDimensionalArrayProperty(name, typeInfo, value);
                }
            }
            else
            {
                if (typeInfo.IsDictionary)
                {
                    property = CreateDictionaryProperty(name, typeInfo, value);
                }
                else
                {
                    if (typeInfo.IsCollection)
                    {
                        property = CreateCollectionProperty(name, typeInfo, value);
                    }
                    else
                    {
                        if (typeInfo.IsEnumerable)
                        {
                            // Actually it would be enough to check if the typeinfo.IsEnumerable is true...
                            property = CreateCollectionProperty(name, typeInfo, value);
                        }
                    }
                }
            }

            ComplexProperty complexProperty;
            if (property == null)
            {
                // special Property not created yet
                if (nonDuplicateValues.TryGetValue(value, out complexProperty))
                {
                    // it was already processed => its recursive
                    if (!complexProperty.IsReferencedMoreThanOnce)
                        complexProperty.ComplexReferenceId = nextReferenceId++; // mark as recursive, if necessary
                    return new ComplexReferenceProperty(name, complexProperty);
                }

                // If nothing was recognized, a complex type will be created
                property = new ComplexProperty(name, typeInfo.Type, value);
            }

            // Estimating properties of the complex type
            complexProperty = property as ComplexProperty;
            if (complexProperty != null)
            {
                nonDuplicateValues.Add(value, complexProperty);

                IList<PropertyInfo> propertyInfos = _propertyProvider.GetProperties(typeInfo);
                foreach (PropertyInfo propertyInfo in propertyInfos)
                {
                    object subValue = propertyInfo.GetValue(value, _emptyObjectArray);

                    Property subProperty = CreateProperty(propertyInfo.Name, subValue);

                    complexProperty.Properties.Add(subProperty);
                }
            }
            return property;
        }

        private Property CreateCollectionProperty(string name, TypeInfo info, object value)
        {
            var property = new CollectionProperty(name, info.Type);
            property.ElementType = info.ElementType;

            // Items
            var collection = (ICollection)value;
            foreach (object item in collection)
            {
                Property itemProperty = CreateProperty(null, item);

                property.Items.Add(itemProperty);
            }

            return property;
        }

        private Property CreateDictionaryProperty(string name, TypeInfo info, object value)
        {
            var property = new DictionaryProperty(name, info.Type);

            property.KeyType = info.KeyType;
            property.ValueType = info.ElementType;

            // Items
            var dictionary = (IDictionary)value;
            foreach (DictionaryEntry entry in dictionary)
            {
                Property keyProperty = CreateProperty(null, entry.Key);

                Property valueProperty = CreateProperty(null, entry.Value);

                property.Items.Add(new KeyValueItem(keyProperty, valueProperty));
            }

            return property;
        }

        private Property CreateMultiDimensionalArrayProperty(string name, TypeInfo info, object value)
        {
            var property = new MultiDimensionalArrayProperty(name, info.Type);
            property.ElementType = info.ElementType;

            var analyzer = new ArrayAnalyzer(value);

            // DimensionInfos
            property.DimensionInfos = analyzer.ArrayInfo.DimensionInfos;

            // Items
            foreach (var indexSet in analyzer.GetIndexes())
            {
                object subValue = ((Array)value).GetValue(indexSet);
                Property itemProperty = CreateProperty(null, subValue);

                property.Items.Add(new MultiDimensionalArrayItem(indexSet, itemProperty));
            }
            return property;
        }

        private Property CreateSingleDimensionalArrayProperty(string name, TypeInfo info, object value)
        {
            var property = new SingleDimensionalArrayProperty(name, info.Type);
            property.ElementType = info.ElementType;

            var analyzer = new ArrayAnalyzer(value);

            // Dimensionen
            DimensionInfo dimensionInfo = analyzer.ArrayInfo.DimensionInfos[0];
            property.LowerBound = dimensionInfo.LowerBound;

            // Items
            foreach (object item in analyzer.GetValues())
            {
                Property itemProperty = CreateProperty(null, item);

                property.Items.Add(itemProperty);
            }

            return property;
        }

        private static Property CreateSimpleProperty(string name, TypeInfo typeInfo, object value)
        {
            if (!typeInfo.IsSimple) return null;
            var result = new SimpleProperty(name, typeInfo.Type);
            result.Value = value;
            return result;
        }

    }

}
